package com.jd.easyflow.flow.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;

import com.jd.easyflow.flow.engine.FlowContext;
import com.jd.easyflow.flow.engine.FlowResult;
import com.jd.easyflow.flow.engine.FlowRunner;
import com.jd.easyflow.flow.engine.event.FlowEventTrigger;
import com.jd.easyflow.flow.exception.FlowException;
import com.jd.easyflow.flow.filter.Filter;
import com.jd.easyflow.flow.model.parser.FlowParser;

/**
 * 
 * @author liyuliang5
 *
 */
public class Flow {
    
    public static final String DOLLAR = "$";

	private String id;
	
	private String name;
	
	private String[] startNodeIds;

	private List<FlowNode> nodeList;

	private Map<String, Object> properties = new ConcurrentHashMap<>();

	private Map<String, FlowNode> nodeMap;
	/**
	 * relation of node id and index.
	 */
	private Map<String, Integer> nodeIndexMap;
	
	private FlowEventTrigger eventTrigger = new FlowEventTrigger();
	
	private FlowRunner runner;
	
	private List<Filter<FlowContext, FlowResult>> filters;
	
    private List<Filter<Triple<FlowNode, NodeContext, FlowContext>, NodeContext>> nodeFilters;
    
    /**
     * nodeAction is not standard model. define here for performance.
     */
    private List<Filter<Pair<NodeContext, FlowContext>, Object>> nodeActionFilters;
    
    private FlowParser flowParser;
    
    public void init(InitContext initContext) {
        if (nodeList != null) {
            for (FlowNode flowNode : nodeList) {
                flowNode.init(initContext, this);
            }
        }
    }


	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public List<FlowNode> getNodeList() {
		return nodeList;
	}

	public void initNodeList(List<FlowNode> nodeList) {
		for (FlowNode node : nodeList) {
		    addNode(node);
		}
	}

	public Map<String, Object> getProperties() {
		return properties;
	}

	public void setProperties(Map<String, Object> properties) {
		this.properties = properties;
	}
	
    public void putProperties(Map<String, Object> properties) {
        if (properties != null) {
            this.properties.putAll(properties);
        }
    }

	public Map<String, FlowNode> getNodeMap() {
		return nodeMap;
	}

	public void setNodeMap(Map<String, FlowNode> nodeMap) {
		this.nodeMap = nodeMap;
	}

	public void setProperty(String key, Object value) {
		properties.put(key, value);
	}
	
	public <T>T getProperty(String key) {
	    return (T) properties.get(key);
	}

	public void addNode(FlowNode node) {
	    if (node.getId().startsWith(DOLLAR)) {
	        throw new IllegalArgumentException("Node ID CANNOT start with $");
	    }
		if (this.nodeList == null) {
			this.nodeList = new ArrayList<>();
		}
		if (this.nodeMap == null) {
			this.nodeMap =  new HashMap<>();
		}
		if (this.nodeMap.containsKey(node.getId())) {
		    throw new FlowException("Node:" + node.getId() + " duplicate");
		}
		if (this.nodeIndexMap == null) {
		    this.nodeIndexMap =  new HashMap<>();
		}
		this.nodeList.add(node);
		this.nodeMap.put(node.getId(), node);
		this.nodeIndexMap.put(node.getId(), this.nodeList.size() - 1);
	}

	public FlowNode getNode(String nodeId) {
		return this.nodeMap.get(nodeId);
	}
	
	public int getNodeIndex(String nodeId) {
	    return this.nodeIndexMap.get(nodeId);
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

    public FlowEventTrigger getEventTrigger() {
        return eventTrigger;
    }

    public void setEventTrigger(FlowEventTrigger eventTrigger) {
        this.eventTrigger = eventTrigger;
    }
    
    public void triggerEvent(String eventType, FlowContext context) {
        this.eventTrigger.triggerEvent(eventType, context);
    }
    
    public void triggerEvent(String eventType, Object eventData, FlowContext context, boolean catchThrowable) {
        this.eventTrigger.triggerEvent(eventType, eventData, context, catchThrowable);
    }

    public String[] getStartNodeIds() {
        return startNodeIds;
    }

    public void setStartNodeIds(String[] startNodeIds) {
        this.startNodeIds = startNodeIds;
    }

    public List<Filter<FlowContext, FlowResult>> getFilters() {
        return filters;
    }

    public void setFilters(List<Filter<FlowContext, FlowResult>> filters) {
        this.filters = filters;
    }
    
    public void addFilter(Filter<FlowContext, FlowResult> filter) {
        if (this.filters == null) {
            this.filters = new ArrayList<Filter<FlowContext,FlowResult>>();
        }
        this.filters.add(filter);
    }

    public List<Filter<Triple<FlowNode, NodeContext, FlowContext>, NodeContext>> getNodeFilters() {
        return nodeFilters;
    }

    public void setNodeFilters(List<Filter<Triple<FlowNode, NodeContext, FlowContext>, NodeContext>> nodeFilters) {
        this.nodeFilters = nodeFilters;
    }
    
    public void addNodeFilter(Filter<Triple<FlowNode, NodeContext, FlowContext>, NodeContext> filter) {
        if (this.nodeFilters == null) {
            this.nodeFilters = new ArrayList<Filter<Triple<FlowNode, NodeContext, FlowContext>, NodeContext>>();
        }
        this.nodeFilters.add(filter);
    }

    public List<Filter<Pair<NodeContext, FlowContext>, Object>> getNodeActionFilters() {
        return nodeActionFilters;
    }

    public void addNodeActionFilter(Filter<Pair<NodeContext, FlowContext>, Object> filter) {
        if (this.nodeActionFilters == null) {
            this.nodeActionFilters = new ArrayList<Filter<Pair<NodeContext, FlowContext>, Object>>();
        }
        this.nodeActionFilters.add(filter);
    }

    public FlowRunner getRunner() {
        return runner;
    }

    public void setRunner(FlowRunner runner) {
        this.runner = runner;
    }
    
    public String stringify() {
        return flowParser.stringify(this);
    }


    public FlowParser getFlowParser() {
        return flowParser;
    }


    public void setFlowParser(FlowParser flowParser) {
        this.flowParser = flowParser;
    }


    public void setNodeList(List<FlowNode> nodeList) {
        this.nodeList = nodeList;
    }


    public void setNodeActionFilters(List<Filter<Pair<NodeContext, FlowContext>, Object>> nodeActionFilters) {
        this.nodeActionFilters = nodeActionFilters;
    }

    
    
    
    
}
